# -*- coding: utf-8 -*-


# cmap2owl -- Helper application to convert from concept maps to OWL ontologies
# Copyright (c) 2008-2013  Rodrigo Rizzi Starr
#  
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#  
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#  
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


'''
Wraps the standard prolog interface

@author: Rodrigo Rizzi Starr
@copyright: Copyright © 2008-2013 Rodrigo Rizzi Starr
@license: MIT License
@contact: rodrigo.starr@gmail.com
'''


import time

# swi-prolog  interface
import pyswip 

def modify(func):
    "Updates the modification time in the prolog class"
    def wrapper(*arg):
        "Renew the modification time"
        Prolog.lastModification = time.time()
        res = func(*arg)
        return res
    
    return wrapper
    
class Prolog:
    """Wraps the original prolog class so some additional features 
    can be added, like modification time of the knowledge base.
    This class is a static class."""
    
    # The database is a static variable, because, in fact, it is all global
    db = pyswip.Prolog()
    
    # This holds the asserted facts, so they can be retracted to clean the db
    facts = []
    
    # This holds the time the base was last modified
    lastModification = 0.0
    
    # All commands to the base can be logged if this is writtable
    logger = None
    
    def __init__(self):
        pass
    
    @modify
    def asserta(cls, assertion, catcherrors=True):
        "Assert a new fact in the beginning of the database"
        cls.facts.append(assertion)
        cls.plLogger('asserta', assertion)
        cls.db.asserta(assertion.encode('utf8'), catcherrors)
    asserta = classmethod(asserta)    
    
    @modify
    def assertz(cls, assertion, catcherrors=True):
        "Assert a new fact in the end of the database"
        cls.facts.append(assertion)
        cls.plLogger('assertz', assertion)
        cls.db.assertz(assertion.encode('utf8'), catcherrors)
    assertz = classmethod(assertz)
    
    @modify
    def dynamic(cls, term, catcherrors=True):
        "Change the characteristic of a functor"
        cls.plLogger('dynamic', term)
        cls.db.dynamic(term.encode('utf8'), catcherrors)
    dynamic = classmethod(dynamic)
    
    @modify
    def retract(cls, term, catcherrors=True):
        "Remove a fact from the database"
        cls.plLogger('retract', term)
        cls.db.retract(term.encode('utf8'), catcherrors)
    retract = classmethod(retract)
    
    @modify
    def retractall(cls, term, catcherrors=True):
        "Remove a fact from the database"
        cls.plLogger('retractall', term)
        cls.db.retractall(term.encode('utf8'), catcherrors)
    retractall = classmethod(retractall)
    
    @modify
    def consult(cls, filename, catcherrors=True):
        "Load a file in the database"
        cls.plLogger('consult', filename.join("''"))
        cls.db.consult(filename.encode('utf8'), catcherrors)
    consult = classmethod(consult)

    def query(cls, query, maxresult=-1, catcherrors=True, normalize=True):
        "Query something in the database"
        cls.plLogger(query)
        #assert cls.initialized        
        return QueryWrapper(query, cls.db.query(query.encode('utf8'), 
                                                maxresult, 
                                                catcherrors, 
                                                normalize))()
    query = classmethod(query)
    
    @modify
    def clean(cls, catcherrors=True):
        "Erases everything that was put in the database"
        for term in Prolog.facts:
            cls.retract(term, catcherrors)
        cls.facts = []
    clean = classmethod(clean)
    
    def isUpToDate(cls, timestamp):
        "Returns true if the informed timestamp is newer than the db timestamp"
        return cls.lastModification < timestamp
    isUpToDate = classmethod(isUpToDate)
    
    
    def plLogger(cls, type, content = ''):
        if cls.logger != None:
            if content == '':
                cls.logger.write('%s.\n' % type)
            else:
                cls.logger.write('%s((%s)).\n' % (type, content))
            cls.logger.flush()
    plLogger = classmethod(plLogger)

    
class QueryWrapper(object):
    "Wraps the result of a query so we can encode and decode it correctly."
    
    def __init__(self, query, generator):
        self.query = query
        self.generator = generator
        
    def __call__(self):
        "Returns a generator wrapping the generator"
        while True:
            next = self.generator.next()
            
            converted = {}
            if isinstance(next, dict) > 0: 
                for key, value in next.iteritems():
                    if isinstance(value, basestring):
                        converted[key] = value.decode('utf8')
                    elif isinstance(value, (list, tuple)):
                        converted[key] = [str(el).decode('utf8') for el in value]
                    else:
                        converted[key] = value
            elif isinstance(next, str):
                converted = next.decode('utf8')
            else:
                converted = next    
                    
            yield converted
    
    
    
    
